package it.antonio.ilpizze.mock;

import it.antonio.ilpizze.aop.Advice;
import it.antonio.ilpizze.aop.Invocation;
import it.antonio.ilpizze.mock.matchers.EqualArgumentMatcher;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class MockHandler implements Advice{
	
	private Expectation<?> defaultExpectation;
	private Verification verification;
	private List<Pair<Invocation, Expectation<?>>> stubbedInvocations = new ArrayList<Pair<Invocation,Expectation<?>>>();
	private List<List<ArgumentMatcher>> stubbedMatchers = new ArrayList<List<ArgumentMatcher>>();
	
	private List<Invocation> invocations = new LinkedList<Invocation>();
	
	
	public MockHandler(Expectation<?> defaultExpectation) {
		this.defaultExpectation = defaultExpectation;
	}

	@Override
	public Object onMethod(Invocation invocation) throws Throwable {
		if(Progress.isVerify()){
			
			List<ArgumentMatcher> matchers = Progress.matchers();
			if(matchers.isEmpty()){
				matchers = new ArrayList<ArgumentMatcher>();
				for(Object argument: invocation.getArguments()){
					matchers.add(new EqualArgumentMatcher(argument));
				}
			} 
			
			String error = verification.verify(invocation.getMethod(), matchers,  invocations);
			Progress.stopVerifiy();
			
			if(error != null) throw new AssertionError("VERIFICATION: " + error);
			this.verification = null;
			
			return null;
		} else {
			Progress.startStubbing(invocation);
			
			invocations.add(invocation);
			for(int i = 0; i < stubbedInvocations.size(); i++){
				Invocation stubbed = stubbedInvocations.get(i).first();
				Expectation<?> expectation = stubbedInvocations.get(i).second();
				if(areEquals(stubbed, invocation, stubbedMatchers.get(i))) {
					return expectation.expect(invocation);
				}
			}
			
			return defaultExpectation.expect(invocation);	
		}
	}

	public void registerExpectation(Expectation<?> expectation){
		Invocation invocation = Progress.invocation();
		Pair<Invocation, Expectation<?>> stubbedInvocation = Pair.from(invocation, expectation);
		stubbedInvocations.add(stubbedInvocation);
		
		List<ArgumentMatcher> matchers = Progress.matchers();
		
		if(matchers.isEmpty()){
			matchers = new ArrayList<ArgumentMatcher>();
			for(Object argument: invocation.getArguments()){
				matchers.add(new EqualArgumentMatcher(argument));
			}
		} 
		stubbedMatchers.add(matchers);	
		
		invocations.remove(invocations.size() - 1); // remove last invocation if stubbing
		
		Progress.stopStubbing();
		
	}
	
	private boolean areEquals(Invocation stubbed, Invocation current, List<ArgumentMatcher> matchers) {
		
		if(!stubbed.getMethod().equals(current.getMethod())){
			return false;
		}
		
		for(int i = 0; i < matchers.size(); i++){
			if(!matchers.get(i).matches(current.getArguments()[i])){
				return false;
			}
		}
		return true;
	}

	public void verifiy(Verification verification) {
		this.verification = verification;
	}

	private static class Pair<K,V> {
		private K first;
		private V second;

		public Pair(K first, V second) {
			this.first = first;
			this.second = second;
		}
		public static <K,V,KK extends K, VV extends V> Pair<K, V> from(KK first, VV second){
			return new Pair<K, V>(first, second);
		}
		public K first() {
			return first;
		}

		public V second() {
			return second;
		}
	}	
		
}
